package hh.engine;

import hh.EmptyUtil;
import hh.LogStack;
import hh.util.Asserts;
import hh.util.CommonUtil;
import hh.util.IoUtil;
import hh.util.StringUtil;
import hh.util.coll.CollUtil;
import hh.visit.TemplateWriter;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
import java.util.stream.Collectors;

@Data
@Slf4j
public class Engine {
    
    public static final String OUT = "o";
    
    String templateJavaFile =
            "package {package};\n" +
                    "{import}\n" +
                    "/**\n" +
                    " * auto generate by hh\n" +
                    " */\n" +
                    "public final class {className} {interfaces}{\n" +
                    "    public void doExecute(){\n" +
                    "        {methodCode}\n" +
                    "    }\n" +
                    "}";
    
    
    private String defaultPackage = "hh.mygen";
    String pkg = defaultPackage;
    String outputDir;
    String sourceDir;
    String templateFiles;
    
    List<Table> tables = new ArrayList<>();
    
    boolean debugEnable;
    Context context;
    List<JavaFile> javaFiles = new ArrayList<>();
    
    public void execute() {
        log.info("HH ORM Start!!!");
        
        List<String> files = null;
        log.info("load template files: {}", templateFiles);
        if (StringUtil.isEmpty(templateFiles)) {
            files = listJspFiles();
            log.info("template files not assign, load from dir: {}", getSourceDir());
        } else {
            files = Arrays.asList(templateFiles.split(","));
        }
        log.info("Template files: {}", files);
        for (String templateFile : files) {
            // 未编译，未运行的类源文件
            // 我们需要设置 table， outputPath，这样就可以输出了
            JavaFile javaFile = parseTemplateFile(templateFile);
            log.info("parse template success:{}", templateFile);
            javaFiles.add(javaFile);
        }
        
        if (CollUtil.isEmpty(tables)) tables = context.getTables();
        Asserts.True(CollUtil.isEmpty(tables), "tables=null");
        log.info("Database tables size={}", tables.size());
        for (Table table : tables) {
            for (JavaFile javaFile : javaFiles) {
                try {
                    TemplateWriter visitor = javaFile.compileAndNew(table);
                    if (debugEnable) log.info("java源文件：\n{}", javaFile.getSourceCode());
                    visitor.setSourceCode(javaFile.getSourceCode());
                    visitor.setTemplateFile(javaFile.getTemplateFile());
                    visitor.setContext(context);
                    table.accept(visitor);
                } catch (Exception e) {
                    log.error("错误：" + e.getMessage(), e);
                    System.out.println(javaFile.sourceCode);
                    throw new RuntimeException(e);
                }
            }
        }
        
    }
    
    private List<String> listJspFiles() {
        String sourceDir = getSourceDir();
        Asserts.True(StringUtil.isEmpty(sourceDir), "jsp模板存放位置：sourceDir未指定");
        File file = new File(sourceDir);
        Asserts.True(!file.exists(), "jsp模板存放位置：sourceDir不存在 {}", sourceDir);
        
        List<File> files = IoUtil.listFiles(new ArrayList<>(), file);
        return files.stream().filter(e -> e.getName().endsWith(".jsp"))
                .map(File::getAbsolutePath).collect(Collectors.toList());
    }
    
    private JavaFile parseTemplateFile(String templateFile) {
        File f = new File(templateFile);
        if (!f.exists())
            f = new File(sourceDir, templateFile);
        String content = IoUtil.readToString(f);
        Asserts.empty(content, "文件：{} 内容为空或者不存在", f.getAbsolutePath());
        Reader reader = new Reader(content);
        JavaFile javaFile = generateJava(Parser.parse(reader));
        javaFile.setTemplateFile(templateFile);
        javaFile.setEngine(this);
        return javaFile;
    }
    
    private JavaFile generateJava(Nodes node) {
        JavaFile javaFile = new JavaFile();
        List<String> imports = node.getList().stream().filter(e -> e instanceof Nodes.Import)
                .flatMap(e -> Arrays.stream(e.getBody().split(",")))
                .collect(Collectors.toList());
        
        String source = templateJavaFile
                .replace("{package}", getPkg())
                .replace("{methodCode}", generateCode(node));
        javaFile.setUnfinishedSourceCode(source);
        javaFile.setImports(imports);
        javaFile.setPkg(getPkg());
        javaFile.setNodes(node);
        return javaFile;
    }
    
    private String generateCode(Nodes node) {
        StringBuilder sb = new StringBuilder();
        for (Nodes.Node n : node.getList()) {
            if (n instanceof Nodes.Import) continue;
            
            if (n instanceof Nodes.Expr) {
                if (StringUtil.isNotEmpty(n.getBody()))
                    sb.append(EmptyUtil.newLine).append(StringUtil.spaces(8)).append(writeMethod(n.getBody()));
                continue;
            }
            
            String body = n.getBody().replace("\r\n", "\n");
            if (n instanceof Nodes.Text) {
                sb.append(EmptyUtil.newLine).append(StringUtil.spaces(8))
                        .append(writeMethodString(body));
                continue;
            }
            
            // script
            if (StringUtil.isNotEmpty(body))
                sb.append(EmptyUtil.newLine).append(StringUtil.spaces(8)).append(body);
            
        }
        
        return sb.toString();
    }
    
    private String writeMethod(String methodBody) {
        return OUT + ".write(" + methodBody + ");";
    }
    
    private String writeMethodString(String string) {
        String s = string.replace("\"", "\\\"").replace("\n", "\\n");
        return OUT + ".write(\"" + s + "\");";
    }
    
    public String getPkg() {
        return StringUtil.isEmpty(pkg) ? defaultPackage : pkg;
    }
    
    public void setContext(Context context) {
        this.context = context;
        context.setEngine(this);
    }
}
